# _*_ coding: utf-8 _*_
"""
author: lianGang
email: 1542652663@qq.com
"""

# 读取嵌套和可变长二进制数据

polys = [
[ (1.0, 2.5), (3.5, 4.0), (2.5, 1.5) ],
[ (7.0, 1.2), (5.1, 3.0), (0.5, 7.5), (0.8, 9.0) ],
[ (3.4, 6.3), (1.2, 0.5), (4.6, 9.2) ],
]

# 现在假设这个数据被编码到一个以下列头部开始的二进制文件中去了
import struct
import itertools

# 写样例数据到文件 后续要使用

def write_polys(filename, polys):
    # determine bounding bot
    flattened = list(itertools.chain(*polys))
    min_x = min(x for x, y in flattened)
    max_x = max(x for x, y in flattened)
    min_y = max(y for y, y in flattened)
    max_y = max(y for y, y in flattened)
    with open(filename, 'wb') as f:
        f.write(struct.pack('<iddddi', 0x1234,
                            min_x, min_y,
                            max_x, max_y,
                            len(polys)))
        for poly in polys:
            size = len(poly) * struct.calcsize('<dd')
            f.write(struct.pack('<i', size +4))
            for pt in poly:
                f.write(struct.pack('<dd', *pt))


def read_polys(filename):
    with open(filename, 'rb') as f:
        # read the header
        header = f.read(40)
        file_code, min_x, min_y, max_x, max_y, num_polys = struct.unpack('<iddddi', header)
        polys = []
        for n in range(num_polys):
            pbytes, = struct.unpack('<i', f.read(4))
            poly = []
            for m in range(pbytes // 16):
                pt = struct.unpack('<dd', f.read(16))
                poly.append(pt)
            polys.append(poly)
    return polys


filename = 'data.b'
# write_polys(filename, polys)
polys2 = read_polys(filename)

# 改善第一步 使用描述器表示每个结构字段， 使用基础类Structure 接受字节数据并存储再内部的内存缓冲中， 使用了内存视图 memoryview

class StructField:
    """
    descriptor representing a simple structure field
    """
    def __init__(self, format, offset):
        self.format = format
        self.offset = offset

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            r = struct.unpack_from(self.format, instance._buffer, self.offset)
            return r[0] if len(r) == 1 else r


# class Structure:
#     def __init__(self, bytedata):
#         self._buffer = memoryview(bytedata)
#
#
# class PolyHeader(Structure):
#     file_code = StructField('<i', 0)
#     min_x = StructField('<d', 4)
#     min_y = StructField('<d', 12)
#     max_x = StructField('<d', 20)
#     max_y = StructField('<d', 28)
#     num_polys = StructField('<i', 36)
#
#
f = open('data.b', 'rb')
# phead = PolyHeader(f.read(40))


# 改进2 重复使用StructField 类定义冗余 可考虑类装饰器或元类
class NestedStruct:
    """
    descriptor representing a nested structure
    """
    def __init__(self, name, struct_type, offset):
        self.name = name
        self.struct_type = struct_type
        self.offset = offset

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            data = instance._buffer[self.offset:
                   self.offset+self.struct_type.struct_size]
            result = self.struct_type(data)
            # save resulting structure back on instacne to avoid further recomputation of this step
            setattr(instance, self.name, result)
            return result


class StructureMeta(type):
    """
    Metaclass that automatically creates StructField descriptors
    """
    def __init__(self, clsname, bases, clsdict):
        fields = getattr(self, '_fields_', [])
        byte_order = ''
        offset = 0
        for format, fieldname in fields:
            if isinstance(format, StructureMeta):
                setattr(self, fieldname, NestedStruct(fieldname, format, offset))
                offset += format.struct_size
            else:
                if format.startswith(('<', '>', '!', '@')):
                    byte_order = format[0]
                    format = format[1:]
                format = byte_order + format
                setattr(self, fieldname, StructField(format, offset))
                offset += struct.calcsize(format)
        setattr(self, 'struct_size', offset)


class Structure(metaclass=StructureMeta):
    def __init__(self, bytedata):
        self._buffer = memoryview(bytedata)

    @classmethod
    def from_file(cls, f):
        return cls(f.read(cls.struct_size))


class Point(Structure):
    _fields_ = [
        ('<d', 'x'),
        ('d', 'y')
    ]


class PolyHeader(Structure):
    _fields_ = [
        ('<i', 'file_code'),
        (Point, 'min'),
        (Point, 'max'),
        ('i', 'num_polys')
    ]

# phead2 = PolyHeader.from_file(f)


# 改进3 处理变长部分
# 增加一个类 表示字节数据， 新增工具函数来解析内容
class SizeRecord:
    def __init__(self, bytedata):
        self._buffer = memoryview(bytedata)

    @classmethod
    def from_file(cls, f, size_fmt, includes_size=True):
        sz_nbytes = struct.calcsize(size_fmt)
        sz_bytes = f.read(sz_nbytes)
        sz, = struct.unpack(size_fmt, sz_bytes)
        buf = f.read(sz - includes_size * sz_nbytes)
        return cls(buf)

    def iter_as(self, code):
        if isinstance(code, str):
            s = struct.Struct(code)
            for off in range(0, len(self._buffer), s.size):
                yield s.unpack_from(self._buffer, off)
        elif isinstance(code, StructureMeta):
            size = code.struct_size
            for off in range(0, len(self._buffer), size):
                data = self._buffer[off: off+size]
                yield code(data)

phead = PolyHeader.from_file(f)

# 改进4 修正read_polys函数
def read_polys(field):
    polys = []
    with open(filename, 'rb') as f:
        phead = PolyHeader.from_file(f)
        for n in range(phead.num_polys):
            rec = SizeRecord.from_file(f, '<i')
            poly = [(p.x, p.y) for p in rec.iter_as(Point)]
            polys.append(poly)
    return polys
